package com.hwtx.form.domain.def.meta.config;

import cn.hutool.extra.spring.SpringUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.hwtx.form.domain.FormConstants;
import com.hwtx.form.domain.def.FormConfigExt;
import com.hwtx.form.domain.def.meta.Api;
import com.hwtx.form.domain.def.meta.Component;
import com.hwtx.form.domain.handle.SelectItemsInfo;
import com.hwtx.form.domain.handle.datamodel.DataModel;
import com.hwtx.form.domain.handle.datamodel.DataModelDetail;
import com.hwtx.form.domain.repo.DataModelRepo;
import com.hwtx.form.domain.repo.FormConfigRepo;
import com.hwtx.form.domain.service.CommonApiInvoker;
import com.hwtx.form.domain.vo.FormData;
import com.hwtx.form.persistence.entity.ApiDef;
import com.hwtx.form.persistence.entity.FormConfigQuery;
import com.hwtx.form.util.FormUtil;
import com.hwtx.form.util.JacksonUtil;
import io.geekidea.boot.framework.exception.BusinessException;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.util.*;
import java.util.stream.Collectors;

import static com.hwtx.form.domain.FormConstants.*;
import static com.hwtx.form.domain.def.meta.ChainedSelect.SOURCE;
import static com.hwtx.form.domain.handle.datamodel.DataModel.isSqlModel;
import static com.hwtx.form.domain.handle.datamodel.DataModelAction.ONLY_TABLE;
import static com.hwtx.form.util.FormUtil.*;
import static io.geekidea.boot.generator.constant.GeneratorConstant.GET;

public interface FormComponentConfig {

    String FIELDS = "fields";
    String F_TABLE = "fTable";
    String G_FIELDS = "gFields";

    static FormComponentConfig getFormComponentConfig(Map<String, Object> formData) {
        Integer ctrType = getIntPostValue(formData, "ctrType");
        if (ctrType == null) {
            throw new BusinessException("组件类型不能为空");
        }
        return getFormComponentConfig(ctrType);
    }

    static FormComponentConfig getFormComponentConfig(Integer ctrType) {
        if (ctrType == null) {
            return null;
        }
        FormConstants.FormComponentType componentType = FormConstants.FormComponentType.of(ctrType);
        if (componentType == null) {
            throw new BusinessException("组件类型不存在");
        }
        return componentType.getConfig();
    }

    static Component.Select getApiSelect(String domain, Object title, String placeholder, String name, String paramKey,
                                         String paramValue, Api api, String context) {
        return getApiSelect(domain, title, placeholder, name, paramKey, paramValue, false, api, context);
    }

    static Map<String, Object> getComExt(String type, String formItemExt) {
        FormConfigExt formConfigExt = FormUtil.getFormConfigExt(formItemExt);
        if (formConfigExt == null) {
            return null;
        }
        FormConfigExt.FormConfigCtr formConfigCtr = FormUtil.getFormConfigCtr(type, formConfigExt);
        if (formConfigCtr == null) {
            return null;
        }
        return formConfigCtr.getComExt();
    }

    static Component.Select getApiSelect(String domain, Object label, String placeholder, String name, String paramKey, String paramValue,
                                         boolean includeFields, Api api, String context) {
        Component.Select select = new Component.Select();
        select.setLabel(label);
        select.setPlaceholder(placeholder);
        select.setFormBatProp();
        select.setSearchable(true);
        select.setClearable(true);
        select.setName(name);
        select.setRequired(true);
        select.setSource(api);
        // change 事件处理
        Component.OnEvent onEvent = new Component.OnEvent();
        Component.ChangeEvent changeEvent = new Component.ChangeEvent();
        List<Component.Action> actions = Lists.newArrayList();
        if (includeFields) {
            actions.add(Component.Action.getAjaxAction(Api.builder().method(HTTP_GET)
                    .url(domain + context + "/form/app/formConfig/serviceOutputSize?ctrType=${ctrType}&apiCode=" + paramValue)
                    .sendOn(paramValue).build(), "outSize", true));
            actions.add(Component.Action.getBoolAction(Component.Action.SHOW, G_FIELDS, "${outSize.result>1}"));
            actions.add(Component.Action.getBoolAction(Component.Action.HIDDEN, G_FIELDS, "${outSize.result<2}"));
            actions.add(Component.Action.getBoolAction(Component.Action.CLEAR, FIELDS, "${outSize.result<2 && outSize.result!=-1}"));
            actions.add(Component.Action.getBoolAction(Component.Action.CLEAR, F_TABLE, "${outSize.result<2 && outSize.result!=-1}"));
        }
        actions.add(Component.Action.getStopPropagationAction("${ISEMPTY(" + paramValue + ")}"));

        Component.Action dialogAction = new Component.Action();
        dialogAction.setActionType("dialog");
        Component.Dialog dialog = new Component.Dialog();
        dialog.setTitle("参数配置");
        dialog.setCloseOnEsc(true);

        Component.ServiceBody serviceBody = new Component.ServiceBody();
        String schemaUrl = domain + context + "/form/action/load/formConfig/loadControllerConfig?" + "id=${id}&ctrType=${ctrType}&type=${type}&" + paramKey + "=" + paramValue;
        serviceBody.setSchemaApi(Api.builder().method(HTTP_GET).url(schemaUrl).sendOn(paramValue).build());
        dialog.setBody(List.of(serviceBody));
        dialogAction.setDialog(dialog);
        actions.add(dialogAction);

        changeEvent.setActions(actions);
        onEvent.setChange(changeEvent);
        select.setOnEvent(onEvent);
        return select;
    }

    static ComponentParamInfo getParamInfo(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof String) {
            ComponentParamInfo componentParamInfo = new ComponentParamInfo();
            componentParamInfo.setCode(value.toString());
            return componentParamInfo;
        }
        return JacksonUtil.json2Object(JacksonUtil.object2json(value), ComponentParamInfo.class);
    }

    static List<Component.Select> getFieldSelect(String domain, String modelName, String urlContext) {
        boolean multiple = CollectionUtils.isNotEmpty(SpringUtil.getBean(DataModelRepo.class).getRelates(modelName));
        if (!multiple) {
            Component.Select select = new Component.Select();
            select.setName(FIELDS);
            select.setLabel("#i18n{field}");
            select.setId(FIELDS);
            select.setSearchable(true);
            select.setCheckAll(true);
            select.setMultiple(true);
            select.setClearable(true);
            select.setRequired(true);
            select.setFormBatProp();
            select.setSource(Api.builder().method(HTTP_GET).url(domain + urlContext + "/form/app/dataModel/modelTableFields?modelName=${modelName}").sendOn("this.modelName").build());
            return List.of(select);
        }
        List<Component.Select> selects = new ArrayList<>();
        Component.Select select = new Component.Select();
        select.setName(F_TABLE);
        select.setId(F_TABLE);
        select.setLabel("#i18n{table}");
        select.setSearchable(true);
        select.setCheckAll(true);
        select.setClearable(true);
        select.setRequired(true);
        select.setFormBatProp();
        select.setSource(Api.builder().method(HTTP_GET).url(domain + urlContext + "/form/app/dataModel/relateTables?modelName=${modelName}&" + ONLY_TABLE + "=true").sendOn("this.modelName").build());
        selects.add(select);

        select = new Component.Select();
        select.setName(FIELDS);
        select.setId(FIELDS);
        select.setLabel(false);
        select.setPlaceholder("#i18n{field}");
        select.setSearchable(true);
        select.setRequired(true);
        select.setCheckAll(true);
        select.setMultiple(true);
        select.setClearable(true);
        select.setSource(Api.builder().method(HTTP_GET).url(domain + urlContext + "/form/app/dataModel/modelTableFields?modelName=${modelName}&table=${" + F_TABLE + "}").sendOn("this.modelName").build());
        selects.add(select);
        return selects;
    }

    static Component.Select getSourceConfig(String domain, Object label, String urlContext) {
        return getSelect(SOURCE, label, "#i18n{source}", Api.builder().method(GET).url(domain + urlContext + FormConstants.API_DEF_API).build(), null);
    }

    static Component.Select getSelect(String name, Object label, String placeholder, Api api, List<com.hwtx.form.domain.def.Select.Option> options) {
        Component.Select select = new Component.Select();
        select.setName(name);
        select.setPlaceholder(placeholder);
        if (label != null) {
            select.setLabel(label);
        }
        select.setSearchable(true);
        select.setRequired(true);
        select.setClearable(true);
        if (api != null) {
            select.setSource(api);
        }
        if (options != null) {
            select.setOptions(options);
        }
        return select;
    }

    static void fillInputSource(FormData formData, Map<String, Object> ret) {
        String source = getStrPostValue(formData.getData(), SOURCE);
        if (StringUtils.isNotBlank(source)) {
            ret.put(SOURCE, source);
        }
    }

    default ComponentParamInfo getParamInfo(String serviceKey, Map<String, Object> comExt) {
        Object value = comExt.get(serviceKey);
        if (value == null) {
            return null;
        }
        return getParamInfo(value);
    }

    default Map<String, Object> getFieldsData(Map<String, Object> formData) {
        Map<String, Object> comExt = Maps.newHashMap();
        String fields = getStrPostValue(formData, FIELDS);
        if (StringUtils.isNotBlank(fields)) {
            comExt.put(FIELDS, fields);
        }

        String f_table = getStrPostValue(formData, F_TABLE);
        if (StringUtils.isNotBlank(f_table)) {
            comExt.put(F_TABLE, f_table);
        }
        return comExt;
    }

    default Map<String, Object> getInputComponentParamInfo(Map<String, Object> formData, String type, Map<String, Object> comExt) {
        Map<String, Object> ret = Maps.newHashMap();
        String code = getStrPostValue(formData, type);
        if (comExt.containsKey(type)) {
            ComponentParamInfo componentParamInfo = FormComponentConfig.getParamInfo(comExt.get(type));
            if (componentParamInfo != null) {
                if (!Objects.equals(componentParamInfo.code, code)) {
                    componentParamInfo.code = code;
                    ret.put(type, componentParamInfo);
                } else {
                    ret.put(type, componentParamInfo);
                }
            } else {
                componentParamInfo = new ComponentParamInfo();
                componentParamInfo.code = code;
                componentParamInfo.type = type;
                ret.put(type, componentParamInfo);
            }
        } else {
            ComponentParamInfo componentParamInfo = new ComponentParamInfo();
            componentParamInfo.code = code;
            componentParamInfo.type = type;
            ret.put(type, componentParamInfo);
        }
        return ret;
    }

    default String buildInputOutputConfig(String domain, List<Component> components, String context) {
        Component.Form form = new Component.Form();
        form.setWrapWithPanel(false);
        form.setCanAccessSuperData(false);
        form.setApi(Api.builder().method(HTTP_POST).url(domain + context + "/form/app/formConfig/saveComExtConfig").build());
        form.setBody(components);
        return JacksonUtil.object2json(form);
    }

    default List<Component> getComponentParamsConfig(String code, String comType, Map<String, Object> formData) {
        return buildParamForm(code, comType, formData, apiCode -> {
            ApiDef apiDef = FormUtil.getApiDef(apiCode);
            List<String> inputParams = CommonApiInvoker.getInputParamNames(apiDef);
            List<String> outputParams = CommonApiInvoker.getOutputParamNames(apiDef);
            return Pair.of(inputParams, outputParams);
        });
    }

    default List<Component> buildParamForm(String code, String comType, Map<String, Object> formData, ParmaInfoCallback parmaInfoCallback) {
        List<Component> formComponents = Lists.newArrayList();
        ComponentParamInfo componentParamInfo = getParamInfoMap(formData, List.of(comType)).get(comType);
        Map<String, String> inputParamMapping = Map.of();
        Map<String, String> outParamMapping = Map.of();
        if (componentParamInfo != null) {
            if (componentParamInfo.getInput() != null) {
                inputParamMapping = componentParamInfo.getInput().stream().collect(Collectors.toMap(ParamMapping::getKey, mapping -> {
                    if (mapping.getAlias() != null) {
                        return mapping.getAlias();
                    }
                    return "";
                }));
            }
            if (componentParamInfo.getOutput() != null) {
                outParamMapping = componentParamInfo.getOutput().stream().collect(Collectors.toMap(ParamMapping::getKey, mapping -> {
                    if (mapping.getAlias() != null) {
                        return mapping.getAlias();
                    }
                    return "";
                }));
            }
        }
        Pair<List<String>, List<String>> apiParamNames = parmaInfoCallback.inputsAndOutputs(code);
        List<String> inputParams = apiParamNames.getKey();
        formComponents.addAll(getGroups(inputParams, true, inputParamMapping));
        List<String> outputParams = apiParamNames.getValue();
        formComponents.addAll(getGroups(outputParams, false, outParamMapping));
        formComponents.add(Component.Hidden.buildHidden("apiCode", code));
        formComponents.add(Component.Hidden.buildHidden("comType", comType));
        formComponents.add(Component.Hidden.buildHidden("type", getStrPostValue(formData, "type")));
        formComponents.add(Component.Hidden.buildHidden("cId", getStrPostValue(formData, "id")));
        return formComponents;
    }

    default Map<String, ComponentParamInfo> getParamInfoMap(Map<String, Object> formData, List<String> comKey) {
        FormConfigRepo formConfigRepo = SpringUtil.getBean(FormConfigRepo.class);
        Long id = getLongPostValue(formData, "id");
        FormConfigQuery formConfigQuery = formConfigRepo.getFormConfigQuery(id);
        String type = getStrPostValue(formData, "type");
        Map<String, ComponentParamInfo> paramInfoMap = Maps.newHashMap();
        if (formConfigQuery != null) {
            FormConfigExt formConfigExt = FormUtil.getFormConfigExt(formConfigQuery.getExt());
            if (formConfigExt != null && type != null) {
                FormConfigExt.FormConfigCtr formConfigCtr = getFormConfigCtr(type, formConfigExt);
                if (formConfigCtr != null && formConfigCtr.getComExt() != null) {
                    Map<String, Object> comExt = formConfigCtr.getComExt();
                    comKey.stream().filter(comExt::containsKey).forEach(key -> paramInfoMap.put(key, getParamInfo(comExt.get(key))));
                }
            }
        }
        return paramInfoMap;
    }

    default Map<String, Object> getComponentParamInfo(Map<String, Object> formData) {
        String code = getStrPostValue(formData, "apiCode");
        String comType = getStrPostValue(formData, "comType");
        String type = getStrPostValue(formData, "type");
        if (StringUtils.isBlank(code) || StringUtils.isBlank(comType)) {
            throw new BusinessException("请选择组件");
        }
        ComponentParamInfo componentParamInfo = new ComponentParamInfo();
        componentParamInfo.code = code;
        componentParamInfo.type = comType;
        List<String> inputParams;
        List<String> outputParams;
        if (!Objects.equals(type, QUERY)) {
            ApiDef apiDef = FormUtil.getApiDef(code);
            inputParams = CommonApiInvoker.getInputParamNames(apiDef);
            outputParams = CommonApiInvoker.getOutputParamNames(apiDef);
        } else {
            DataModel dataModel = getDataModel(code);
            if (!isSqlModel(dataModel.getModelType())) {
                throw new RuntimeException("数据模型非SQL类型");
            }
            inputParams = extractSqlVariables(dataModel.getSqlContent());
            outputParams = getDataModelDetails(code).stream().map(DataModelDetail::getName).toList();
        }
        componentParamInfo.input = inputParams.stream().map(inputParam -> {
            String alias = getStrPostValue(formData, inputParam + "Mapping");
            ParamMapping paramMapping = new ParamMapping();
            paramMapping.setKey(inputParam);
            paramMapping.setAlias(alias);
            return paramMapping;
        }).toList();

        componentParamInfo.output = outputParams.stream().map(inputParam -> {
            String alias = getStrPostValue(formData, inputParam + "Mapping");
            ParamMapping paramMapping = new ParamMapping();
            paramMapping.setKey(inputParam);
            paramMapping.setAlias(alias);
            return paramMapping;
        }).toList();
        return Map.of(comType, componentParamInfo);
    }

    default Map<String, Object> getParamInfoCode(FormConfigExt.FormConfigCtr formConfigCtr, Map<String, Boolean> comNames) {
        Map<String, Object> comExt = formConfigCtr.getComExt();
        if (comExt == null) {
            return null;
        }
        Map<String, Object> ret = Maps.newHashMap();
        comNames.forEach((comName, hasParamInfo) -> {
            Object apiParamInfo = comExt.get(comName);
            if (!hasParamInfo) {
                ret.put(comName, apiParamInfo);
            }
            if (apiParamInfo != null && hasParamInfo) {
                ComponentParamInfo componentParamInfo = getParamInfo(apiParamInfo);
                if (componentParamInfo != null) {
                    ret.put(comName, componentParamInfo.getCode());
                }
            }
        });
        return ret;
    }

    private List<Component.Group> getGroups(List<String> keys, boolean isInput, Map<String, String> paramMapping) {

        return keys.stream().map(key -> {
            Component.Group group = new Component.Group();
            List<Component> components = Lists.newArrayList();
            group.setBody(components);
            Component.Input input = new Component.Input();
            input.setType(Component.INPUT_TEXT);
            input.setFormBatProp();
            input.setName(key);
            input.setValue(key);
            input.setLabel(isInput ? "输入" : "输出");
            components.add(input);
            input = new Component.Input();
            input.setType(Component.INPUT_TEXT);
            input.setName(key + "Mapping");
            if (paramMapping.get(key) != null) {
                input.setValue(paramMapping.get(key));
            }
            input.setPlaceholder("映射别名");
            input.setLabel(false);
            components.add(input);
            return group;
        }).toList();
    }

    default Map<String, List<String>> getFields(Map<String, Object> ext) {
        return Map.of();
    }

    Map<String, Object> getInputConfigParams(Map<String, Object> comExt, FormData formData);

    OutputConfig getOutputConfigParams(String type, FormConfigExt.FormConfigCtr formConfigCtr);

    String getConfig(String type, Map<String, Object> formData);

    default Map<String, Object> comExt(Map<String, Object> formData) {
        return null;
    }

    default Map<String, Object> invokeReadService(SelectItemsInfo.SelectItemInfo selectItemInfo, Map<String, Object> data, Map<String, String[]> parameters) {
        return null;
    }

    default ComponentParamInfo getInvokeComponentParamInfo(Map<String, Object> comExt) {
        return null;
    }

    default ComponentDataModelParamInfo getDataModelParamInfo(Map<String, Object> comExt) {
        return null;
    }

    default void invokeWriteService(Map<String, Object> comExt, SelectItemsInfo.SelectItemInfo item, Map<String, Object> parameters) {
    }


    interface ParmaInfoCallback {
        Pair<List<String>, List<String>> inputsAndOutputs(String code);
    }

    @Builder
    @Getter
    class OutputConfig {
        Map<String, Object> params;
        List<String> notIncludeKeys;
    }

    @Data
    class ComponentParamInfo {
        String code;
        String type;
        List<ParamMapping> input;
        List<ParamMapping> output;
    }

    @Data
    class ComponentDataModelParamInfo {
        String modelName;
        String table;
        List<ParamMapping> input;
        List<ParamMapping> output;
    }

    @Data
    @EqualsAndHashCode(of = {"key", "alias"})
    class ParamMapping {
        String key;
        String alias;
        String remark;
    }

    @Builder
    @Getter
    class ApiInvokeParam {
        ComponentParamInfo componentParamInfo;
        FormConstants.FormComponentType componentType;
        FormComponentConfig formComponentConfig;
        Map<String, Set<String>> fields;
        String column;
    }
}